package sm2ext

import (
	"encoding/asn1"
	"encoding/hex"
	"encoding/pem"
	"errors"
	"fmt"
	"github.com/tjfoc/gmsm/sm2"
	"io/ioutil"
	"math/big"
	"reflect"
)

var oidSM2 = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
var oidSM2PrimeFields = asn1.ObjectIdentifier{1, 2, 840, 10045, 1, 1}

type sm2CurveParams struct {
	Versoin int
	Base    struct {
		Algorithm asn1.ObjectIdentifier
		P         *big.Int
	}
	AB struct {
		A []byte
		B []byte
	}
	G []byte
	N *big.Int
	H int
}

type pkcs8_openssl struct {
	Version int
	Algo    struct {
		Algorithm asn1.ObjectIdentifier
		//AlgoParams asn1.RawValue //AlgorithmIdentifier
		AlgoParams sm2CurveParams
	}

	PrivateKey []byte
}

type sm2PrivateKey_openssl struct {
	Version    int
	PrivateKey []byte
	PublicKey  asn1.BitString `asn1:"optional,explicit,tag:1"`
}

func ParseSm2PrivateKey_openssl(der []byte) (*sm2.PrivateKey, error) {
	var privKey sm2PrivateKey_openssl

	if _, err := asn1.Unmarshal(der, &privKey); err != nil {
		return nil, errors.New("x509: failed to parse SM2 private key: " + err.Error())
	}
	curve := sm2.P256Sm2()
	k := new(big.Int).SetBytes(privKey.PrivateKey)
	curveOrder := curve.Params().N
	if k.Cmp(curveOrder) >= 0 {
		return nil, errors.New("x509: invalid elliptic curve private key value")
	}
	priv := new(sm2.PrivateKey)
	priv.Curve = curve
	priv.D = k
	privateKey := make([]byte, (curveOrder.BitLen()+7)/8)
	for len(privKey.PrivateKey) > len(privateKey) {
		if privKey.PrivateKey[0] != 0 {
			return nil, errors.New("x509: invalid private key length")
		}
		privKey.PrivateKey = privKey.PrivateKey[1:]
	}
	copy(privateKey[len(privateKey)-len(privKey.PrivateKey):], privKey.PrivateKey)
	priv.X, priv.Y = curve.ScalarBaseMult(privateKey)
	return priv, nil
}

func ParsePKCS8UnecryptedPrivateKey_openssl(der []byte) (*sm2.PrivateKey, error) {
	var privKey pkcs8_openssl

	if _, err := asn1.Unmarshal(der, &privKey); err != nil {
		return nil, err
	}
	if !reflect.DeepEqual(privKey.Algo.Algorithm, oidSM2) {
		return nil, errors.New("x509: not sm2 elliptic curve")
	}
	return ParseSm2PrivateKey_openssl(privKey.PrivateKey)
}

func ParsePKCS8PrivateKey_openssl(der, pwd []byte) (*sm2.PrivateKey, error) {
	if pwd == nil || len(pwd) == 0 {

		return ParsePKCS8UnecryptedPrivateKey_openssl(der)
	}
	//return testParsePKCS8EcryptedPrivateKey(der, pwd)
	return nil, fmt.Errorf("Unsupported")
}

func Read_private_pem_file_openssl(pemFileName, passwd string) (*sm2.PrivateKey, error) {
	pemTmp, err := ioutil.ReadFile(pemFileName)
	if err != nil {
		fmt.Println("read file error:", err)
		return nil, err
	}

	var block *pem.Block
	block, _ = pem.Decode(pemTmp)
	if block == nil {
		return nil, errors.New("failed to decode private key")
	}

	priKey, err := ParsePKCS8PrivateKey_openssl(block.Bytes, []byte(passwd))

	return priKey, err
}

func MakePKCS8UnecryptedPrivateKey_openssl(privKey *sm2.PrivateKey) ([]byte, error) {
	curve := sm2.P256Sm2()
	curveParams := curve.Params()

	g := []byte{0x04}
	g = append(g, curveParams.Gx.Bytes()...)
	g = append(g, curveParams.Gy.Bytes()...)

	var err error
	privKeyPack := pkcs8_openssl{}
	privKeyPack.Version = 0
	privKeyPack.Algo.Algorithm = oidSM2
	privKeyPack.Algo.AlgoParams.Versoin = 1
	privKeyPack.Algo.AlgoParams.Base.Algorithm = oidSM2PrimeFields
	privKeyPack.Algo.AlgoParams.Base.P = curveParams.P
	privKeyPack.Algo.AlgoParams.AB.A, _ = hex.DecodeString("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC")
	privKeyPack.Algo.AlgoParams.AB.B = curveParams.B.Bytes()
	privKeyPack.Algo.AlgoParams.G = g
	privKeyPack.Algo.AlgoParams.H = 1
	privKeyPack.Algo.AlgoParams.N = curveParams.N

	keyData := sm2PrivateKey_openssl{
		Version: 1,
	}
	keyData.PrivateKey = append([]byte(nil), privKey.D.Bytes()...)

	var buff []byte
	buff = append(buff, 04)
	buff = append(buff, privKey.X.Bytes()...)
	buff = append(buff, privKey.Y.Bytes()...)
	keyData.PublicKey.BitLength = len(buff) * 8
	keyData.PublicKey.Bytes = buff

	buff, err = asn1.Marshal(keyData)
	//fmt.Printf("private bit string: %x\n", buff)
	privKeyPack.PrivateKey = buff

	buff, err = asn1.Marshal(privKeyPack)
	//fmt.Printf("private key pkcs8(openssl format): %x\n", buff)
	return buff, err
}
